﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using ETModel;

namespace ETTools {
    internal class OpcodeInfo {
        public string Name;
        public int Opcode;
    }

    public static class Program {

        public static NameValueCollection ParseStartArgs(string[] args) {
            NameValueCollection nv = new NameValueCollection();
            foreach (var arg in args) {
                ParseStartArgs(arg, nv);
            }
            return nv;
        }
        public static void ParseStartArgs(string args, NameValueCollection nv) {
            var inName = false;
            var inValue = false;
            var name = string.Empty;
            var value = string.Empty;
            var valHasMark = false;
            for (int i = 0; i < args.Length; i++) {
                var chr = args[i];
                if (inName) {
                    if (chr == ':') {
                        inName = false;//键结束
                        inValue = true;//值开始

                        nv[name] = string.Empty;
                    } else {
                        name += chr;
                    }
                    continue;
                }

                if (inValue) {
                    if (string.IsNullOrEmpty(value) && chr == '"') {
                        //第一个字符是双引号,则记录标志并开始下一个
                        valHasMark = true;
                        continue;
                    }
                    if ((valHasMark && chr == '"')
                        || (!valHasMark && chr == ' ')) {
                        //如果有双引号,并且当前是双引号,说明是结束了
                        //如果没双引号,并且当前是空格,说明是结束了
                        nv[name] = value;
                        //清空键和值
                        value = name = string.Empty;
                        inValue = false;//值结束
                        continue;
                    }
                    //没结束的都算内容,拼上去
                    value += chr;
                    continue;
                }

                //都不是,则说明可能是要开始新的键了
                if (chr == '/') {
                    inName = true;
                    continue;
                }

            }

            if (!string.IsNullOrEmpty(name)) {
                //结束了,发现还有键没清空,就是最后没有空格,这里要补充下结束
                nv[name] = value;
                //清空键和值
                value = name = string.Empty;
                inName = inValue = false;//都结束
            }
        }

        /// <summary>
        /// 输入参数格式:  /name1:value1  /name2:"值中有空格的就需要用双引号"  /name3:"value3...后面的以此类推" <para/>
        /// 可选参数:<para/>
        /// /InnerMessageProtoDir:服务器内部通讯消息proto文件所在目录<para/>
        /// /OuterMessageProtoDir:服务器和客户端通讯消息proto文件所在目录<para/>
        /// /HotfixMessageProtoDir:热更消息proto文件所在目录<para/>
        /// /InnerMessageProtoName:服务器内部通讯消息proto文件名,不传将不生成<para/>
        /// /OuterMessageProtoName:服务器和客户端通讯消息proto文件名,不传将不生成<para/>
        /// /HotfixMessageProtoName:热更消息proto文件名,不传将不生成<para/>
        /// /InnerMessageOutput:服务器内部通讯消息类存放目录<para/>
        /// /OuterMessageOutput:服务器和客户端通讯消息类存放目录<para/>
        /// /HotfixMessageOutput:热更消息类存放目录<para/>
        /// /StartOpCode:操作码的起始,非ET系统级的,需要从20000开始!<para/>
        /// </summary>
        /// <param name="args"></param>
        public static void Main(string[] args) {

            string InnerMessageProtoDir = "./";
            string OuterMessageProtoDir = "./";
            string HotfixMessageProtoDir = "./";
            string InnerMessageProtoName = "";
            string OuterMessageProtoName = "";
            string HotfixMessageProtoName = "";//HotfixMessage.proto
            string InnerMessageOutput = "../Server/Model/Module/Message/";
            string OuterMessageOutput = "../Unity/Assets/Model/ET/Module/Message/";
            string HotfixMessageOutput = "../Unity/Assets/Hotfix/ET/Module/Message/";
            string InnerMessageJSOutput = "";
            string OuterMessageJSOutput = "";
            string HotfixMessageJSOutput = "";
            int StartOpCode = 0;
            string ModelNS = "ETModel";
            string HotfixNS = "ETHotfix";

            var argNv = ParseStartArgs(args);
            if (!string.IsNullOrEmpty(argNv["InnerMessageProtoDir"])) InnerMessageProtoDir = argNv["InnerMessageProtoDir"].Trim();
            if (!string.IsNullOrEmpty(argNv["OuterMessageProtoDir"])) OuterMessageProtoDir = argNv["OuterMessageProtoDir"].Trim();
            if (!string.IsNullOrEmpty(argNv["HotfixMessageProtoDir"])) HotfixMessageProtoDir = argNv["HotfixMessageProtoDir"].Trim();

            if (!string.IsNullOrEmpty(argNv["InnerMessageProtoName"])) InnerMessageProtoName = argNv["InnerMessageProtoName"].Trim();
            if (!string.IsNullOrEmpty(argNv["OuterMessageProtoName"])) OuterMessageProtoName = argNv["OuterMessageProtoName"].Trim();
            if (!string.IsNullOrEmpty(argNv["HotfixMessageProtoName"])) HotfixMessageProtoName = argNv["HotfixMessageProtoName"].Trim();

            if (!string.IsNullOrEmpty(argNv["InnerMessageOutput"])) InnerMessageOutput = argNv["InnerMessageOutput"].Trim();
            if (!string.IsNullOrEmpty(argNv["OuterMessageOutput"])) OuterMessageOutput = argNv["OuterMessageOutput"].Trim();
            if (!string.IsNullOrEmpty(argNv["HotfixMessageOutput"])) HotfixMessageOutput = argNv["HotfixMessageOutput"].Trim();

            if (!string.IsNullOrEmpty(argNv["InnerMessageJSOutput"])) InnerMessageJSOutput = argNv["InnerMessageJSOutput"].Trim();
            if (!string.IsNullOrEmpty(argNv["OuterMessageJSOutput"])) OuterMessageJSOutput = argNv["OuterMessageJSOutput"].Trim();
            if (!string.IsNullOrEmpty(argNv["HotfixMessageJSOutput"])) HotfixMessageJSOutput = argNv["HotfixMessageJSOutput"].Trim();

            if (!string.IsNullOrEmpty(argNv["StartOpCode"])) StartOpCode = Convert.ToInt32(argNv["StartOpCode"]);

            if (!string.IsNullOrEmpty(argNv["ModelNS"])) ModelNS = argNv["ModelNS"].Trim();
            if (!string.IsNullOrEmpty(argNv["HotfixNS"])) HotfixNS = argNv["HotfixNS"].Trim();


            string protoc = "";
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                protoc = "protoc.exe";
            } else {
                protoc = "protoc";
            }

            if (!string.IsNullOrEmpty(InnerMessageProtoName)) {
                if (!Directory.Exists(InnerMessageOutput)) Directory.CreateDirectory(InnerMessageOutput);
                ProcessHelper.Run(protoc, "--csharp_out=\"" + OuterMessageOutput + "\" --proto_path=\"" + OuterMessageProtoDir + "\" " + OuterMessageProtoName, waitExit: true);
            }
            if (!string.IsNullOrEmpty(OuterMessageProtoName)) {
                if (!Directory.Exists(OuterMessageOutput)) Directory.CreateDirectory(OuterMessageOutput);
                ProcessHelper.Run(protoc, "--csharp_out=\"" + InnerMessageOutput + "\" --proto_path=\"" + InnerMessageProtoDir + "\" " + InnerMessageProtoName, waitExit: true);
            }
            if (!string.IsNullOrEmpty(HotfixMessageProtoName)) {
                if (!Directory.Exists(HotfixMessageOutput)) Directory.CreateDirectory(HotfixMessageOutput);
                ProcessHelper.Run(protoc, "--csharp_out=\"" + HotfixMessageOutput + "\" --proto_path=\"" + HotfixMessageProtoDir + "\" " + HotfixMessageProtoName, waitExit: true);
            }

            string protoName;
            if (!string.IsNullOrEmpty(InnerMessageProtoName)) {
                msgOpcode.Clear();
                protoName = Path.GetFileNameWithoutExtension(InnerMessageProtoName);
                Proto2CS(ModelNS, OuterMessageProtoDir, InnerMessageProtoName, OuterMessageOutput, protoName + ".Part", protoName + "Opcode", StartOpCode + 100);
                GenerateOpcode(ModelNS, protoName + "Opcode", InnerMessageOutput, protoName + ".Opcode");
                if (!string.IsNullOrEmpty(InnerMessageJSOutput)) {
                    if (!Directory.Exists(InnerMessageJSOutput)) Directory.CreateDirectory(InnerMessageJSOutput);
                    GenerateOpcodeForTS(protoName + "Opcode", InnerMessageJSOutput, protoName + "Opcode");
                }

                // InnerMessage.proto生成cs代码
                //InnerProto2CS.Build("ETModel", InnerMessageProtoDir, InnerMessageProtoName, InnerMessageOutput, OpCodePrefix + "InnerOpcode", StartOpCode + 1000);
                //因为想统一使用protobuf传输数据,不用mongo的序列化了(inner上面这种代码生成方式是为了mongo序列化用)
            }

            if (!string.IsNullOrEmpty(OuterMessageProtoName)) {
                msgOpcode.Clear();
                protoName = Path.GetFileNameWithoutExtension(OuterMessageProtoName);
                Proto2CS(ModelNS, OuterMessageProtoDir, OuterMessageProtoName, OuterMessageOutput, protoName + ".Part", protoName + "Opcode", StartOpCode + 1000);
                GenerateOpcode(ModelNS, protoName + "Opcode", OuterMessageOutput, protoName + ".Opcode");
                if (!string.IsNullOrEmpty(OuterMessageJSOutput)) {
                    if (!Directory.Exists(OuterMessageJSOutput)) Directory.CreateDirectory(OuterMessageJSOutput);
                    GenerateOpcodeForTS(protoName + "Opcode", OuterMessageJSOutput, protoName + "Opcode");
                }
            }

            if (!string.IsNullOrEmpty(HotfixMessageProtoName)) {
                msgOpcode.Clear();
                protoName = Path.GetFileNameWithoutExtension(HotfixMessageProtoName);
                Proto2CS(HotfixNS, HotfixMessageProtoDir, HotfixMessageProtoName, HotfixMessageOutput, protoName + ".Part", protoName + "Opcode", StartOpCode + 10000);
                GenerateOpcode(HotfixNS, protoName + "Opcode", HotfixMessageOutput, protoName + ".Opcode");
                if (!string.IsNullOrEmpty(HotfixMessageJSOutput)) {
                    if (!Directory.Exists(HotfixMessageJSOutput)) Directory.CreateDirectory(HotfixMessageJSOutput);
                    GenerateOpcodeForTS(protoName + "Opcode", HotfixMessageJSOutput, protoName + "Opcode");
                }
            }



            Console.WriteLine("proto2cs succeed!");
        }

        private const string protoPath = ".";
        private static readonly char[] splitChars = { ' ', '\t' };
        private static readonly List<OpcodeInfo> msgOpcode = new List<OpcodeInfo>();

        /// <summary>
        /// 生成proto类的部分类
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="protoDir"></param>
        /// <param name="protoName"></param>
        /// <param name="outputPath"></param>
        /// <param name="outputFileName">不包含.cs后缀</param>
        /// <param name="opcodeClassName">操作码类名</param>
        /// <param name="startOpcode"></param>
        /// <param name="isClient"></param>
        public static void Proto2CS(string ns, string protoDir, string protoName, string outputPath,string outputFileName, string opcodeClassName, int startOpcode, bool isClient = true) {
            msgOpcode.Clear();
            string proto = Path.Combine(protoPath, protoDir, protoName);

            string s = File.ReadAllText(proto);

            StringBuilder sb = new StringBuilder();
            sb.Append("using ETModel;\n");
            sb.Append($"namespace {ns}\n");
            sb.Append("{\n");

            bool isMsgStart = false;

            foreach (string line in s.Split('\n')) {
                string newline = line.Trim();

                if (newline == "") {
                    continue;
                }

                if (newline.StartsWith("//")) {
                    sb.Append($"{newline}\n");
                }

                if (newline.StartsWith("message")) {
                    string parentClass = "";
                    isMsgStart = true;
                    string msgName = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries)[1];
                    string[] ss = newline.Split(new[] { "//" }, StringSplitOptions.RemoveEmptyEntries);

                    if (ss.Length == 2) {
                        parentClass = ss[1].Trim();
                    } else {
                        parentClass = "";
                    }

                    msgOpcode.Add(new OpcodeInfo() { Name = msgName, Opcode = ++startOpcode });

                    sb.Append($"\t[Message({opcodeClassName}.{msgName})]\n");
                    sb.Append($"\tpublic partial class {msgName} ");
                    if (parentClass != "") {
                        sb.Append($": {parentClass} ");
                    }

                    sb.Append("{}\n\n");
                }

                if (isMsgStart && newline == "}") {
                    isMsgStart = false;
                }
            }

            sb.Append("}\n");

            string csPath = Path.Combine(outputPath, outputFileName + ".cs");
            File.WriteAllText(csPath, sb.ToString());
        }

        /// <summary>
        /// 生成操作码类
        /// </summary>
        /// <param name="ns"></param>
        /// <param name="opcodeClassName">操作码类名</param>
        /// <param name="outputPath">输出目录</param>
        /// <param name="outputFileName">不包含.cs后缀</param>
        private static void GenerateOpcode(string ns, string opcodeClassName, string outputPath, string outputFileName) {

            StringBuilder allOp = new StringBuilder();
            string allOpSplit = "";

            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"using System.Collections.Generic;");
            sb.AppendLine($"namespace {ns}");
            sb.AppendLine("{");
            sb.AppendLine($"    public static partial class {opcodeClassName}");
            sb.AppendLine("    {");
            foreach (OpcodeInfo info in msgOpcode) {
                sb.AppendLine($"        public const ushort {info.Name} = {info.Opcode};");

                allOp.Append(allOpSplit);
                allOp.Append($"{{ \"{info.Name}\", {info.Opcode} }}");
                allOpSplit = ",";
            }
            sb.AppendLine($"        public static Dictionary<string, ushort> AllOpcode = new Dictionary<string, ushort> {{ {allOp.ToString()} }};");
            sb.AppendLine("    }");
            sb.AppendLine("}");

            string csPath = Path.Combine(outputPath, outputFileName + ".cs");
            File.WriteAllText(csPath, sb.ToString());
        }

        /// <summary>
        /// 生成操作码类
        /// </summary>
        /// <param name="opcodeClassName">操作码类名</param>
        /// <param name="outputPath">输出目录</param>
        /// <param name="outputFileName">不包含.ts后缀</param>
        private static void GenerateOpcodeForTS(string opcodeClassName, string outputPath, string outputFileName) {

            StringBuilder allOp = new StringBuilder();
            string allOpSplit = "";

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("export class "+ opcodeClassName + " {");
            foreach (OpcodeInfo info in msgOpcode) {
                sb.AppendLine($"    public static {info.Name}:number = {info.Opcode};");

                allOp.Append(allOpSplit);
                allOp.Append($" \"Op_{info.Opcode}\": \"{info.Name}\" ");
                allOpSplit = ",";
            }
            sb.AppendLine($"    public static CodeToName :object = {{ {allOp.ToString()} }};");
            sb.AppendLine("}");

            string csPath = Path.Combine(outputPath, outputFileName + ".ts");
            File.WriteAllText(csPath, sb.ToString());
        }
    }

    public static class InnerProto2CS {
        private const string protoPath = ".";
        private static readonly char[] splitChars = { ' ', '\t' };
        private static readonly List<OpcodeInfo> msgOpcode = new List<OpcodeInfo>();

        public static void Build(string ns, string protoDir, string protoName, string saveDirPath, string opcodeClassName, int startOpcode) {
            msgOpcode.Clear();
            Proto2CS(ns, protoDir, protoName, saveDirPath, opcodeClassName, startOpcode);
            GenerateOpcode(ns, opcodeClassName, saveDirPath);
        }

        public static void Proto2CS(string ns, string protoDir, string protoName, string outputPath, string opcodeClassName, int startOpcode) {
            msgOpcode.Clear();
            string proto = Path.Combine(protoPath, protoDir, protoName);
            string csPath = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(proto) + ".cs");

            string s = File.ReadAllText(proto);

            StringBuilder sb = new StringBuilder();
            sb.Append("using ETModel;\n");
            sb.Append("using System.Collections.Generic;\n");
            sb.Append($"namespace {ns}\n");
            sb.Append("{\n");

            bool isMsgStart = false;
            string parentClass = "";
            foreach (string line in s.Split('\n')) {
                string newline = line.Trim();

                if (newline == "") {
                    continue;
                }

                if (newline.StartsWith("//")) {
                    sb.Append($"{newline}\n");
                }

                if (newline.StartsWith("message")) {
                    parentClass = "";
                    isMsgStart = true;
                    string msgName = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries)[1];
                    string[] ss = newline.Split(new[] { "//" }, StringSplitOptions.RemoveEmptyEntries);

                    if (ss.Length == 2) {
                        parentClass = ss[1].Trim();
                    }

                    msgOpcode.Add(new OpcodeInfo() { Name = msgName, Opcode = ++startOpcode });

                    sb.Append($"\t[Message({opcodeClassName}.{msgName})]\n");
                    sb.Append($"\tpublic partial class {msgName}");
                    if (parentClass == "IActorMessage" || parentClass == "IActorRequest" || parentClass == "IActorResponse" ||
                        parentClass == "IFrameMessage") {
                        sb.Append($": {parentClass}\n");
                    } else if (parentClass != "") {
                        sb.Append($": {parentClass}\n");
                    } else {
                        sb.Append("\n");
                    }

                    continue;
                }

                if (isMsgStart) {
                    if (newline == "{") {
                        sb.Append("\t{\n");
                        continue;
                    }

                    if (newline == "}") {
                        isMsgStart = false;
                        sb.Append("\t}\n\n");
                        continue;
                    }

                    if (newline.Trim().StartsWith("//")) {
                        sb.AppendLine(newline);
                        continue;
                    }

                    if (newline.Trim() != "" && newline != "}") {
                        if (newline.StartsWith("repeated")) {
                            Repeated(sb, ns, newline);
                        } else {
                            Members(sb, newline, true);
                        }
                    }
                }
            }

            sb.Append("}\n");

            File.WriteAllText(csPath, sb.ToString());
        }

        private static void GenerateOpcode(string ns, string outputFileName, string outputPath) {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"namespace {ns}");
            sb.AppendLine("{");
            sb.AppendLine($"\tpublic static partial class {outputFileName}");
            sb.AppendLine("\t{");
            foreach (OpcodeInfo info in msgOpcode) {
                sb.AppendLine($"\t\t public const ushort {info.Name} = {info.Opcode};");
            }

            sb.AppendLine("\t}");
            sb.AppendLine("}");

            string csPath = Path.Combine(outputPath, outputFileName + ".cs");
            File.WriteAllText(csPath, sb.ToString());
        }

        private static void Repeated(StringBuilder sb, string ns, string newline) {
            try {
                int index = newline.IndexOf(";");
                newline = newline.Remove(index);
                string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
                string type = ss[1];
                type = ConvertType(type);
                string name = ss[2];

                sb.Append($"\t\tpublic List<{type}> {name} = new List<{type}>();\n\n");
            } catch (Exception e) {
                Console.WriteLine($"{newline}\n {e}");
            }
        }

        private static string ConvertType(string type) {
            string typeCs = "";
            switch (type) {
                case "int16":
                    typeCs = "short";
                    break;
                case "int32":
                    typeCs = "int";
                    break;
                case "bytes":
                    typeCs = "byte[]";
                    break;
                case "uint32":
                    typeCs = "uint";
                    break;
                case "long":
                    typeCs = "long";
                    break;
                case "int64":
                    typeCs = "long";
                    break;
                case "uint64":
                    typeCs = "ulong";
                    break;
                case "uint16":
                    typeCs = "ushort";
                    break;
                default:
                    typeCs = type;
                    break;
            }

            return typeCs;
        }

        private static void Members(StringBuilder sb, string newline, bool isRequired) {
            try {
                int index = newline.IndexOf(";");
                newline = newline.Remove(index);
                string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
                string type = ss[0];
                string name = ss[1];
                string typeCs = ConvertType(type);

                sb.Append($"\t\tpublic {typeCs} {name} {{ get; set; }}\n\n");
            } catch (Exception e) {
                Console.WriteLine($"{newline}\n {e}");
            }
        }
    }
}